USB Communication
The Human Interface Device (HID) class is used to communicate with the
device. The main benefit is that there are no drivers needed.
The drawback is low communication speed, but this is OK for this use case
as there is not much data transmitted.
Note that the HID protocol differentiates between input reports, output
reports and feature reports.
Input reports (e.g. endpoint 1) are usually periodic, i.e. the operating
system of the master (e.g. Windows) will poll the input report at the
interval defined in the device's configuration descriptor.
It is also possible to read the input report via a control request on
demand (e.g. via endpoint 0), but due to the periodic nature of the input
report, this can lead to bandwidth and buffering issues.
Indeed, while reading input reports via control requests worked in a C#
prototype, it didn't work via the Java HID API.
As a workaround, I decided to (ab-)use the feature report to communicate
with the device instead. The input report is just used for a small period
message which contains the current state and engine speed.
So the size of the (periodic) input report is limited to 3 bytes (8bit
state followed by 16bit engine speed), while the size of the feature
report is set to 32 bytes.
Protocol
The communication is always initiated by the master (PC) and starts with
an 8bit command. The following 31 bytes can be used for data belonging to
the command.
The device always answers with ACK|command (ACK = 0x80 is ORed with
command), followed by a status (0: OK else specific fault status code).
The following 30 bytes are command specific (i.e. containing requested
data).
Transferring data from/to the four channels requires channel specific
commands. The 2bit channel information is then coded into the two
lowermost bits of the command.
E.g. INIT_READ_CH0 is 8 (8|0) and INIT_READ_CH3 is 11 (8|3).
Note that 16bit and 32bit values are transferred little endian (lowest
byte first, highest byte last).
Fault status codes
FAULT_OK |
0 |
FAULT_CTR_MISMATCH |
1 |
FAULT_NVRAM_SIZE |
2 |
FAULT_NVRAM_INVALID |
3 |
FAULT_NVRAM_BUSY |
4 |
Commands
Write channel data to device
|
INIT_WRITE_CH0 |
0 |
INIT_WRITE_CH1 |
1 |
INIT_WRITE_CH2 |
2 |
INIT_WRITE_CH3 |
3 |
WRITE_CH0 |
4 |
WRITE_CH1 |
5 |
WRITE_CH2 |
6 |
WRITE_CH3 |
7 |
Read channel data from device
|
INIT_READ_CH0 |
8 |
INIT_READ_CH1 |
9 |
INIT_READ_CH2 |
10 |
INIT_READ_CH3 |
11 |
READ_CH0 |
12 |
READ_CH1 |
13 |
READ_CH2 |
14 |
READ_CH3 |
15 |
Write gradient commands
|
INIT_WRITE_GRAD |
0x10 |
WRITE_GRAD |
0x11 |
START_GRAD |
0x12 |
STOP_GRAD |
0x13 |
Read gradient commands |
INIT_READ_GRAD |
0x18 |
READ_GRAD |
0x19 |
NVRAM commands
|
GET_NVRAM_STATE |
0x20 |
WRITE_NVRAM |
0x21 |
READ_NVRAM |
0x22 |
CLEAR_NVRAM |
0x23 |
Engine speed commands
|
START |
0x40 |
STOP |
0x41 |
GET_N |
0x42 |
SET_N |
0x43 |
SET_BIDIR_SETUP |
0x44 |
GET_BIDIR_SETUP |
0x45 |
UPDATE_MODES |
0x48 |
SET_GLITCHES |
0x49 |
Set PWM commands
|
SET_PWM_CH0 |
0x50 |
SET_PWM_CH1 |
0x51 |
SET_PWM_CH2 |
0x52 |
SET_PWM_CH3 |
0x53 |
Get PWM commands
|
GET_PWM_CH0 |
0x54 |
GET_PWM_CH1 |
0x55 |
GET_PWM_CH2 |
0x56 |
GET_PWM_CH3 |
0x57 |
Get Revision command
|
GET_REVISION |
0x7f |
Command details
Get Revision
Reads back the revision from the device.
PC sends
GET_REVISION (U8)
Device answers
GET_REVISION|ACK (U8), FAULT_OK (U8), REVISION (U32)
In the replied revision each byte is a decimal place. E.g. 0x01020310 -> 1.2.3.16
Write data to channel X
Writing channel data has to be initialized with an INIT_WRITE command, where
the device's answer will contain the number N of transfers needed.
The actual data transfer is then done via N READ commands.
PC sends
INIT_WRITE_CHx (U8), NUM_OF_DATA (U16), OFFSET (U32),
1st_EDGE (U8), MODE (U8), NAME (16xU8)
NUM_OF_DATA is the number of U32 values (periods in
internal tick resolution)
OFFSET is the offset of the 1st edge based on the
absolute 0 position (in internal tick resolution)
1st_EDGE is the polarity of this 1st edge (0: falling, 1:
rising)
MODE can be angular (0), time (1) or PWM (2)
NAME is a string of up to 16 ASCII characters, If less
characters are used, the string must be zero terminated.
Device answers
INIT_WRITE_CHx|ACK (U8), FAULT_OK (U8),
NUMBER_OF_PACKETS_NEEDED (U16)
PC sends (NUMBER_OF_PACKETS_NEEDED times)
WRITE_CHx (U8), PACKET_COUNTER (U16), DATA (N*U32)
PACKET_COUNTER is a counter starting with 0 used to
validate the order of packets.
DATA contains the 1 to 7 U32 values (periods in internal
tick resolution)
Device answers (each time)
WRITE_CHx|ACK (U8), STATUS/FAULT (U8), RECEIVED_CTR,
EXPECTED_CTR
In case of an error, the status will be
FAULT_CTR_MISMATCH and the both counters (received/expected) won't match.
Note that an init write command stop the output signal
creation. It has to be started again with the start command.
Read data from channel X
Reading has to be initialized with an INIT_READ command, where the device's
answer will contain the number N of transfers needed.
The actual data transfer is then done via N READ commands.
PC sends
INIT_READ_CHx (U8)
Device answers
INIT_READ_CHx|ACK (U8), FAULT_OK (U8),
NUMBER_OF_PACKETS_NEEDED (U16), NUMBER_OF_DATA (U16), OFFSET (U32), 1st_EDGE
(U8), MODE (U8), NAME (16xU8)
PC sends (NUMBER_OF_PACKETS_NEEDED times)
READ_CHx (U8), PACKET_COUNTER (U16)
Device answers (each time)
READ_CHx|ACK (U8), STATUS/FAULT (U8), RECEIVED_CTR,
EXPECTED_CTR, DATA
Start output signal creation
Start the output after it was stopped e.g. due to writing channel data.
PC sends
START (U8)
Device answers
START|ACK(U8), FAULT_OK (U8)
Set engine speed
Set a new value for engine speed.
PC sends
SET_N (U8), ENGINE_SPEED (S16)
Device answers
SET_N|ACK(U8), FAULT_OK (U8)
Get engine speed
Read back current value for engine speed.
PC sends
GET_N (U8)
Device answers
GET_N|ACK(U8), FAULT_OK (U8), ENGINE_SPEED (S16)
This command is not really needed, as the input report transfers the current
engine speed permanently.
Write Gradient
Send gradient data to the device. Note: when sending gradient data, the
output is not stopped.
PC sends
INIT_WRITE_GRAD (U8), NUM_OF_DATA (U16)
Device answers
INIT_WRITE_GRAD|ACK (U8), FAULT_OK (U8),
NUMBER_OF_PACKETS_NEEDED (U16)
PC sends (NUMBER_OF_PACKETS_NEEDED times)
WRITE_GRAD (U8), PACKET_COUNTER (U16), DATA (N*(U32+S16))
DATA: pairs of U32 period in milliseconds, S16 engine speed
Device answers (each time)
WRITE_GRAD|ACK (U8), STATUS/FAULT (U8), RECEIVED_CTR,
EXPECTED_CTR
Read Gradient
Read back the current gradient from the device.
PC sends
INIT_READ_GRAD (U8)
Device answers
INIT_READ_GRAD|ACK (U8), FAULT_OK (U8),
NUMBER_OF_PACKETS_NEEDED (U16), NUMBER_OF_DATA (u16)
PC sends (NUMBER_OF_PACKETS_NEEDED times)
READ_GRAD (U8), PACKET_COUNTER (U16)
Device answers (each time)
READ_GRAD|ACK (U8), STATUS/FAULT (U8), RECEIVED_CTR,
EXPECTED_CTR, DATA
DATA: pairs of U32 period in milliseconds, S16 engine speed
Start Gradient
Start an engine speed gradient.
PC sends
START_GRAD (U8)
Device answers START_GRAD|HID_CMD_ACK (U8),
HID_FAULT_OK (U8)
Stop Gradient
Stops a running engine speed gradient.
PC sends
STOP_GRAD (U8)
Device answers STOP_GRAD|HID_CMD_ACK (U8),
HID_FAULT_OK (U8)
Write NVRAM
Write the current channel data etc. from device RAM to NVRAM.
PC sends
WRITE_NVRAM (U8)
Device answers
WRITE_NVRAM|ACK (U8), STATUS/FAULT (U8), NUMBER_OF_DATA
(u16)
The device calculates the number of needed bytes and reports OK if writing
to NVRAM is possible.
The number of bytes are just reported for a progress display.
Beware: writing is not immediately finished and has to be polled via command
GET_NVRAM_STATE.
STATUS: FAULT_OK
writing is possible
FAULT_NVM_SIZE not
enough space in NVRAM
FAULT_NVM_BUSY device is busy reading or writing to NVRAM
Read NVRAM
Read channel data etc. from NVRAM to device RAM.
PC sends
READ_NVRAM (U8)
Device answers
READ_NVRAM|ACK (U8), STATUS/FAULT (U8), NUMBER_OF_DATA
(u16)
The device calculates the number of bytes used in NVRAM and reports OK if a
valid header was found in NVRAM.
Beware: writing is not immediately finished and has to be polled via command
GET_NVRAM_STATE.
STATUS: FAULT_OK
reading is possible
FAULT_NVM_INVALID no valid header found
in NVRAM (empty or corrupt) or CRC error in data section
FAULT_NVM_BUSY device is
busy reading or writing to NVRAM
Clear NVRAM
Clear the NVRAM by overwriting the header with zeros.
PC sends
CLEAR_NVRAM (U8)
Device answers
CLEAR_NVRAM|ACK (U8), STATUS/FAULT (U8), NUMBER_OF_DATA
(u16)
The number of bytes are just reported for a progress display. Status will
always be OK
Beware: clearing is not immediately finished and has to be polled via
command GET_NVRAM_STATE.
Get NVRAM state
Read status of NVRAM - also needed during pending read/write/clear
operation.
PC sends
GET_NVRAM_STATE (U8)
Device answers
GET_NVRAM_STATE|ACK (U8), NVRAM_STATE (U8),
NUMBER_OF_DATA (u16)
NVRAM_STATE:
NVRAM_INVALID_ST 0
NVRAM_READY_ST 1
NVRAM_READ_ST 2
NVRAM_WRITE_ST 3
NVRAM_CLEAR_ST 4
During READ/WRITE/CLEAR operation, NUMBER_OF_DATA contains the number of
bytes to be processed, else 0.
Set PWM channel
Sets duty cycle, period and polarity for a PWM channel.
PC sends
SET_PWM_CHx (U8), POLARITY (U8), PERIOD (U32), DUTYPERIOD
(U32)
Device answers
SET_PWM_CHx|ACK (U8), FAULT_OK (U8)
PERIOD and DUTYPERIOD are in timer tick resolution. POLARITY is 0 for low
active and 1 for high active.
Get PWM channel
Reads back duty cycle, period and polarity of a PWM channel.
PC sends
GET_PWM_CHx (U8)
Device answers
GET_PWM_CHx|ACK (U8), FAULT_OK (U8), POLARITY (U8),
PERIOD (U32), DUTYPERIOD (U32)
PERIOD and DUTYPERIOD are in timer tick resolution. POLARITY is 0 for low
active and 1 for high active.
Update Modes
Sets the modes for all four channels at once.
PC sends
UPDATE_MODES (U8), MODE CH0 (U8), MODE CH1 (U8), MODE CH2
(U8), MODE CH3 (U8)
Device answers
UPDATE_MODES|ACK (U8), FAULT_OK (U8)
Supported modes are:
ANGULAR 0
TIME 1
PWM 2
Set BiDir Setup
Sets new device configuration for bidirectional crank simulation.
PC sends
SET_BIDIR_SETUP (U8), REV_ENABLE (U8), BIDIR_ENABLE (U8),
ACTIVE_EDGE (U8), FWD_PERIOD (U32), REV_PERIOD (U32)
Device answers
SET_BIDIR_SETUP|ACK (U8), FAULT_OK (U8)
REV_ENABLE enabled (1) or disables the possibility of negative engine speeds (inverted rotation direction)
BIDIR_ENABLE enables (1) or disables (0) the bidirectional simulation for
channel 0.
ACTIVE_EDGE defines falling edge (0) or rising edge (1) as active edge (i.e.
falling edge means the active period will be low)
FWD_PERIOD defines the active period for forward rotation (in timer ticks)
REV_PERIOD defines the active period for reverse rotation (in timer ticks)
Get BiDir Setup
Reads back device configuration for bidirectional crank simulation.
PC sends
GET_BIDIR_SETUP (U8)
Device answers
GET_BIDIR_SETUP|ACK (U8), FAULT_OK (U8), REV_ENABLE (U8),
BIDIR_ENABLE (U8), ACTIVE_EDGE (U8), FWD_PERIOD (U32), REV_PERIOD (U32)
BIDIR_ENABLE enables (1) or disables (0) the bidirectional simulation for
channel 0.
ACTIVE_EDGE defines falling edge (0) or rising edge (1) as active edge (i.e.
falling edge means the active period will be low)
FWD_PERIOD defines the active period for forward rotation (in timer ticks)
REV_PERIOD defines the active period for reverse rotation (in timer ticks)
Create glitches
Create glitches by pulling the masked channel to the given direction for
a certain time (repeat with period)
PC sends
SET_GLITCHES (U8), CH_ENABLE_MASK (U8), CH_POLARITY_MASK
(U8), COUNT (U8), DURATION (U32), PERIOD (U32)
Device answers
SET_GLITCHES|ACK (U8), FAULT_OK (U8)
CH_ENABLE_MASK: set channel bit (1<<ch) to 1 to create a glitch on the
channel
CH_POLARITY_MASK: pull enabled channel low (0) or high (1)
COUNT: repeat the glitch creation count times.
DURATION: duration of one glitch in timer ticks
PERIOD: interval between two glitches (from start to start) in timer ticks -
must be larger than DURATION
Back to main page